﻿using System;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using GrapeCity.Spread.Sheets.ExcelIO;
using GrapeCity.Windows.SpreadSheet.Data;
using System.Net.Http.Headers;
using System.Collections.ObjectModel;

namespace GrapeCity.ExcelService.Controllers
{
    /// <summary>
    ///  Represent the import API which enable upload a file to be imported on server.
    /// </summary>
    public class ImportController : ApiController
    {
        // Use customized provider to avoid asynchronized IO error for high concurrency
        // -------------------------------
        private class MyMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
        {
            private class FormattingUtilities
            {
                public static string UnquoteToken(string token)
                {
                    if (!string.IsNullOrWhiteSpace(token)
                        && ((token.StartsWith("\"", StringComparison.Ordinal)
                        && token.EndsWith("\"", StringComparison.Ordinal))
                        && (token.Length > 1)))
                    {
                        return token.Substring(1, token.Length - 2);
                    }
                    return token;
                }
            }

            private Collection<bool> _isFormData;

            public MyMultipartFormDataStreamProvider(string rootPath)
                : base(rootPath)
            {
                this._isFormData = new Collection<bool>();
            }

            public MyMultipartFormDataStreamProvider(string rootPath, int bufferSize)
                : base(rootPath, bufferSize)
            {
                this._isFormData = new Collection<bool>();
            }

            public override Task ExecutePostProcessingAsync()
            {
                return Task.Factory.StartNew(() =>
                    Task.WaitAll(base.Contents
                    .Where<HttpContent>((content, index) => this._isFormData[index])
                    .Select<HttpContent, Task>((formContent) =>
                        {
                            ContentDispositionHeaderValue contentDisposition = formContent.Headers.ContentDisposition;
                            string formFieldName = FormattingUtilities.UnquoteToken(contentDisposition.Name) ?? string.Empty;

                            return formContent.ReadAsStringAsync().ContinueWith((t) =>
                            {
                                this.FormData.Add(formFieldName, t.Result);
                            }, TaskContinuationOptions.ExecuteSynchronously);
                        }).ToArray()));
            }

            public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
            {
                string str;
                if (parent == null)
                {
                    throw new ArgumentNullException("parent");
                }
                if (headers == null)
                {
                    throw new ArgumentNullException("headers");
                }

                ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
                if (contentDisposition == null)
                {
                    throw new InvalidOperationException("No Content-Disposition");
                }
                if (!string.IsNullOrEmpty(contentDisposition.FileName))
                {
                    this._isFormData.Add(false);

                    try
                    {
                        string localFileName = this.GetLocalFileName(headers);
                        str = Path.Combine(RootPath, Path.GetFileName(localFileName));
                    }
                    catch (Exception exception)
                    {
                        throw new InvalidOperationException("Invalid local file name", exception);
                    }
                    MultipartFileData item = new MultipartFileData(headers, str);
                    FileData.Add(item);

                    return File.Create(str, BufferSize, FileOptions.None);

                }
                this._isFormData.Add(true);
                return new MemoryStream();
            }
        }
        // -------------------------------

        // POST xsapi/import
        /// <summary>
        ///  Upload the source file to be imported to data module.
        /// </summary>
        /// <returns>
        /// 
        /// </returns>
        public async Task<HttpResponseMessage> Post()
        {
            // Check if the request contains multipart/form-data.
            if (!Request.Content.IsMimeMultipartContent())
            {
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
            }

            string root = null;
            if (HttpContext.Current != null)
            {
                root = HttpContext.Current.Server.MapPath("~/App_Data");
            }
            else
            {
                root = this.Configuration.Properties["Root"] as string;
            }

            var provider = new MyMultipartFormDataStreamProvider(root);

            try
            {
                // Read the form data.
                await Request.Content.ReadAsMultipartAsync(provider);
            }
            catch (Exception e)
            {
                if (e.InnerException != null && (e.InnerException is IOException))
                {
                    e = new InvalidOperationException("The path '~/App_Data' access denied.", e.InnerException);
                }
                return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
            }

            // try process the first file
            var file = provider.FileData.FirstOrDefault();
            if (file == null)
            {
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
            }
            try
            {
                Importer importer = new Importer();
                // detect the file
                var fileName = file.Headers.ContentDisposition.FileName;
                string jsonString = string.Empty;
                try
                {
                    jsonString = this.Import(importer, file.LocalFileName, fileName, provider.FormData);
                    if (jsonString == null || (jsonString is string) && string.IsNullOrEmpty(jsonString as string))
                    {
                        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);                        
                    }
                }
                catch (Exception inner)
                {
                    return CreateResponse(inner.Message, true);
                }
                if (!string.IsNullOrEmpty(importer.ErrorMessage))
                {
                    return CreateResponse(importer.ErrorMessage, true);
                }
                return CreateResponse(jsonString, false);
            }
            finally
            {
                File.Delete(file.LocalFileName);
            }
        }

        private HttpResponseMessage CreateResponse(string jsonString, bool isError)
        {
            StringContent content;
            if (isError)
            {
                content = new StringContent("{\"error\":" + jsonString + "}");
            }
            else
            {
                content = new StringContent("{\"spread\":" + jsonString + "}");
            }
            var response = Request.CreateResponse(HttpStatusCode.OK);
            response.Content = content;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            return response;
        }

        public string Import(Importer importer, string file, string fileName, NameValueCollection data)
        {
            var originalFileName = fileName.Trim('\"');
            var originalFileExtension = Path.GetExtension(originalFileName);
            if (string.IsNullOrEmpty(originalFileExtension))
            {
                return "";
            }
            originalFileExtension = originalFileExtension.Substring(1).ToUpperInvariant();
            switch (originalFileExtension)
            {
                case "XLS":
                case "XLSX":
                    return this.OpenExcel(importer, file, data);
                case "CSV":
                    return this.OpenCsv(importer, file, data);
                case "TXT":
                    return this.OpenText(importer, file, data);
            }
            return "";
        }


        private string OpenExcel(Importer importer, string fileName, NameValueCollection formData)
        {
            var sFlags = formData.Get("ExcelOpenFlags");
            var password = formData.Get("Password");
            ExcelOpenFlags flags;
            if (!Enum.TryParse(sFlags, true, out flags))
            {
                flags = ExcelOpenFlags.NoFlagsSet;
            }
            using (var stream = File.OpenRead(fileName))
            {
                return importer.ImportExcel(stream, flags, password);
            }
        }

        private string OpenText(Importer importer, string fileName, NameValueCollection formData)
        {
            var sFlags = formData.Get("TextFileOpenFlags");
            string encoding = formData.Get("Encoding");
            var rowDelimiter = formData.Get("RowDelimiter");
            var columnDelimiter = formData.Get("ColumnDelimiter");
            var cellDelimiter = formData.Get("CellDelimiter");

            TextFileOpenFlags flags;
            if (!Enum.TryParse(sFlags, true, out flags))
            {
                flags = TextFileOpenFlags.None;
            }
            using (var stream = File.OpenRead(fileName))
            {
                return importer.ImportText(stream, flags, encoding, rowDelimiter, columnDelimiter, cellDelimiter);
            }
        }

        private string OpenCsv(Importer importer, string fileName, NameValueCollection formData)
        {
            var sFlags = formData.Get("TextFileOpenFlags");
            string encoding = formData.Get("Encoding");
            string rowDelimiter = formData.Get("rowDelimiter");
            string columnDelimiter = formData.Get("columnDelimiter");
            string cellDelimiter = formData.Get("cellDelimiter");

            TextFileOpenFlags flags;
            if (!Enum.TryParse(sFlags, true, out flags))
            {
                flags = TextFileOpenFlags.None;
            }
            using (var stream = File.OpenRead(fileName))
            {
                return importer.ImportCsv(stream, flags, encoding, rowDelimiter, columnDelimiter, cellDelimiter);
            }
        }

    }

}
